package com.mrx.xfriendship.service;

import com.mrx.commons.exception.BusinessException;
import com.mrx.commons.util.asserts.Asserts;
import com.mrx.xfriendship.dao.UserMapper;
import com.mrx.xfriendship.event.MailEvent;
import com.mrx.xfriendship.model.dto.MailDTO;
import com.mrx.xfriendship.model.po.UserPO;
import com.mrx.xfriendship.model.vo.PostVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.lang.NonNull;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;

import java.util.Date;
import java.util.regex.Pattern;

/**
 * @author Mr.X
 * @since 2022-11-06 17:45
 */
@Slf4j
@Service
@Validated
@DubboService
@RequiredArgsConstructor
public class MailServiceImpl implements IMailService {

    private final JavaMailSender javaMailSender;

    private final MailProperties mailProperties;

    private static final String SUBJECT_TEMPLATE = "【华夏交友圈】 %s";

    private static final String BIND_MAIL_CONTENT = "亲爱的 %s, 欢迎注册 华夏交友圈, 本次绑定邮箱的验证码为\n%s";

    private static final String RECOVERY_MAIL_CONTENT = "亲爱的 %s, 您的账号 %s 正在尝试找回密码, 点击以下链接将密码重置为 %s, 链接 10 分钟内有效\n%s";

    private final IRedisService redisService;

    private final UserMapper userMapper;

    private final ICaptchaService captchaService;

    private final ApplicationEventPublisher eventPublisher;

    private final PasswordEncoder encoder;

    @Value("${spring.mail.real}")
    private Boolean realSend;

    @Value("${spring.mail.admin}")
    private String adminMail;

    private static final Pattern pattern = Pattern.compile("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$");

    @Override
    public void sendSimpleMail(MailDTO mailDTO) {
        if (!realSend) {
            log.debug("spring.mail.real 为 false, 不发送邮件, 邮件内容为；{}", mailDTO);
            return;
        }
        Asserts.MAIL_ERROR.checkTrue(pattern.matcher(mailDTO.getSendTo()).find());
        SimpleMailMessage message = new SimpleMailMessage();
        checkMail(mailDTO);
        // 邮件发件人
        message.setFrom(mailProperties.getUsername());
        // 邮件收件人 1或多个
        message.setTo(mailDTO.getSendTo().split(","));
        // 邮件主题
        message.setSubject(mailDTO.getSubject());
        // 邮件内容
        message.setText(mailDTO.getText());
        // 邮件发送时间
        message.setSentDate(new Date());
        javaMailSender.send(message);
        log.info("发送邮件成功: {} -> {}", mailProperties.getUsername(), mailDTO.getSendTo());
    }

    @Async
    @EventListener(MailEvent.class)
    public void sendMail(@NonNull MailEvent mailEvent) {
        sendSimpleMail((MailDTO) mailEvent.getSource());
    }

    @Override
    public void sendBindMail(String to, String userName) throws BusinessException {
        MailDTO mail = MailDTO.builder()
                .sendTo(to)
                .subject(String.format(SUBJECT_TEMPLATE, "邮箱绑定"))
                .text(String.format(BIND_MAIL_CONTENT, userName, createCaptcha(userName, to)))
                .build();
        eventPublisher.publishEvent(MailEvent.of(mail));
    }

    @Override
    public void sendRecoveryMail(String server, String userName) {
        UserPO userPO = Asserts.USER_NOT_EXISTS.checkNotNull(userMapper.selectByName(userName));
        String mailStr = Asserts.MAIL_NOT_BIND.checkNotNull(userPO.getMail());
        String tempPassword = RandomStringUtils.randomAlphanumeric(10);
        String code = createCaptcha(userName, tempPassword);
        String text = String.format(
                RECOVERY_MAIL_CONTENT,
                mailStr, userName, tempPassword,
                String.format("http://%s/mail/recoveryConfirm?userName=%s&code=%s", server, userName, code)
        );
        MailDTO mail = MailDTO.builder()
                .sendTo(mailStr)
                .subject(String.format(SUBJECT_TEMPLATE, "邮箱绑定"))
                .text(text)
                .build();
        eventPublisher.publishEvent(MailEvent.of(mail));
    }

    @Override
    public void recoveryConfirm(String userName, String code) {
        String actuallyCode = redisService.getBindMail(userName).orElseThrow(Asserts.RECOVERY_CODE_ERROR::getException);
        Asserts.RECOVERY_CODE_ERROR.checkTrue(actuallyCode.endsWith(code));
        UserPO userPO = userMapper.selectByName(userName);
        String newPassword = actuallyCode.split(":")[0];
        log.debug("更新用户 {} 的 密码为: {}", userPO.getName(), newPassword);
        userPO.setPass(encoder.encode(newPassword));
        userMapper.updateByPrimaryKey(userPO);
    }

    @Override
    public void sendRecoveryMail(String mail) {
        String tempPassword = RandomStringUtils.randomAlphanumeric(10);
        String code = createCaptcha(mail, tempPassword);
        MailDTO mailDTO = MailDTO.builder()
                .sendTo(mail)
                .subject(String.format(SUBJECT_TEMPLATE, "找回密码"))
                .text(String.format("本次找回密码的验证码是 %s", code))
                .build();
        sendSimpleMail(mailDTO);
    }

    @Override
    public void recoveryWithEmailConfirm(String mail, String code) {
        String actuallyCode = redisService.getBindMail(mail).orElseThrow(Asserts.RECOVERY_CODE_ERROR::getException);
        Asserts.RECOVERY_CODE_ERROR.checkTrue(actuallyCode.endsWith(code));
        UserPO userPO = Asserts.NO_SUCH_USER_WITH_THIS_MAIL.checkNotNull(userMapper.selectByEmail(mail));
        String pass = RandomStringUtils.randomAlphanumeric(10);
        log.debug("更新用户 {} 的 密码为: {}", userPO.getName(), pass);
        userPO.setPass(encoder.encode(pass));
        userMapper.updateByPrimaryKeySelective(userPO);
        MailDTO mailDTO = MailDTO.builder()
                .sendTo(mail)
                .subject(String.format(SUBJECT_TEMPLATE, "密码重置成功"))
                .text(String.format("您的新密码为: 【%s】", pass))
                .build();
        sendSimpleMail(mailDTO);
    }

    @Override
    public void sendFeedbackMail(PostVO body) {
        sendSimpleMail(
                MailDTO.builder()
                        .sendTo(adminMail)
                        .subject(String.format(SUBJECT_TEMPLATE, "用户反馈") + body.getTitle())
                        .text(body.getContent())
                        .build()
        );
    }

    private String createCaptcha(String userName, String mail) {
        String code = captchaService.getCaptchaStr();
        redisService.setBindMail(userName, String.format("%s:%s", mail, code));
        return code;
    }

    public void checkMail(MailDTO mailDTO) {
        Assert.notNull(mailDTO, "邮件请求不能为空");
        Assert.notNull(mailDTO.getSendTo(), "邮件收件人不能为空");
        Assert.notNull(mailDTO.getSubject(), "邮件主题不能为空");
        Assert.notNull(mailDTO.getText(), "邮件收件人不能为空");
    }

}
